package com.zbkj.service.manager.impl;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zbkj.dal.base.enums.*;
import com.zbkj.dal.common.exception.CrmebException;
import com.zbkj.dal.common.utils.MoneyUtil;
import com.zbkj.dal.dao.UserBalanceRecordDao;
import com.zbkj.dal.dao.UserDao;
import com.zbkj.dal.dao.UserFundAccountDao;
import com.zbkj.dal.entity.fund.UserFundAccount;
import com.zbkj.dal.entity.user.User;
import com.zbkj.dal.entity.user.UserBalanceRecord;
import com.zbkj.service.manager.FundManager;
import com.zbkj.service.manager.UserManager;
import com.zbkj.service.model.dto.FundUpdateDTO;
import com.zbkj.service.model.dto.SaveUserBalanceRecordDTO;
import com.zbkj.service.model.dto.UseAvailableResultDTO;
import com.zbkj.service.model.front.vo.fund.CrabRoeAccountVO;
import com.zbkj.service.util.DateTimeUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Objects;

@Slf4j
@Component
public class FundManagerImpl implements FundManager {


    @Resource
    private UserDao userDao;
    @Resource
    private UserManager userManager;
    @Resource
    private UserFundAccountDao userFundAccountDao;
    @Resource
    private UserBalanceRecordDao userBalanceRecordDao;


    @Override
    public UserFundAccount fundAccount(Integer userId, String accountType) {
        // 校验用户资金账户状态
        UserFundAccount userFundAccount = this.findByIdAndAccountType(userId, accountType);
        if (Objects.isNull(userFundAccount)) {
            log.error("未查询到userId={}的雅识资金账户", userId);
            throw new CrmebException("用户账户信息异常");
        }
        return userFundAccount;
    }

    @Override
    public CrabRoeAccountVO userCrabRoeAccount(Integer userId) {
        UserFundAccount userFundAccount = this.findByIdAndAccountType(userId, FundAccountType.CRAB_ROE.name());
        if (Objects.isNull(userFundAccount)) {
            throw new CrmebException("为查询到当前用户的雅识账户信息");
        }

        CrabRoeAccountVO crabRoeAccountVO = new CrabRoeAccountVO();
        crabRoeAccountVO.setTotalAmountNum(userFundAccount.getTotalAmount());
        crabRoeAccountVO.setFrozenAmountNum(userFundAccount.getFrozenAmount());
        crabRoeAccountVO.setAvailableAmountNum(userFundAccount.getAvailableAmount());
        crabRoeAccountVO.setTotalAmount(MoneyUtil.prettyFormat(userFundAccount.getTotalAmount()));
        crabRoeAccountVO.setFrozenAmount(MoneyUtil.prettyFormat(userFundAccount.getFrozenAmount()));
        crabRoeAccountVO.setAvailableAmount(MoneyUtil.prettyFormat(userFundAccount.getAvailableAmount()));
        return crabRoeAccountVO;
    }

    @Override
    public UserFundAccount findByIdAndAccountType(Integer userId, String accountType) {
        LambdaQueryWrapper<UserFundAccount> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(UserFundAccount::getUserId, userId);
        wrapper.eq(UserFundAccount::getAccountType, accountType);
        wrapper.eq(UserFundAccount::getDeleted, BooleanEnums.FALSE.getCode());
        return userFundAccountDao.selectOne(wrapper);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addCrabRoeApply(FundUpdateDTO fundUpdateDTO) {
        // 校验用户及账户状态
        Pair<User, UserFundAccount> userPair = this.queryAndCheckFundAccount(fundUpdateDTO.getUserId(), FundAccountType.CRAB_ROE.name());
        UserFundAccount userFundAccount = userPair.getRight();

        // 添加到在途金额中
        userFundAccount.setTotalAmount(userFundAccount.getTotalAmount().add(fundUpdateDTO.getAmount()));
        userFundAccount.setFrozenAmount(userFundAccount.getFrozenAmount().add(fundUpdateDTO.getAmount()));
        this.checkFund(userFundAccount.getFundAccount(), userFundAccount.getTotalAmount(), userFundAccount.getAvailableAmount(), userFundAccount.getFrozenAmount());
        userFundAccountDao.updateById(userFundAccount);

        // 添加在途记录
        SaveUserBalanceRecordDTO saveUserBalanceRecordDTO = SaveUserBalanceRecordDTO.builder().userFundAccount(userFundAccount)
                .status(FundStatusEnums.IN_TRANSIT.getCode()).bizOrderNo(fundUpdateDTO.getBizOrderNo())
                .tradeAmount(fundUpdateDTO.getAmount()).fundType(fundUpdateDTO.getTradeType())
                .fundDirection(FundDirectionEnums.INCOME.getCode()).remark(fundUpdateDTO.getRemark()).build();
        this.saveUserBalanceRecord(saveUserBalanceRecordDTO);

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addCrabRoeApplyCancel(String bizOrderNo) {
        LambdaQueryWrapper<UserBalanceRecord> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(UserBalanceRecord::getLinkId, bizOrderNo);
        wrapper.eq(UserBalanceRecord::getDeleted, BooleanEnums.FALSE.getCode());
        UserBalanceRecord userBalanceRecord = userBalanceRecordDao.selectOne(wrapper);
        if (Objects.isNull(userBalanceRecord)) {
            log.error("未查询到对应的流水记录" + bizOrderNo);
            return;
        }
        if (StringUtils.equals(userBalanceRecord.getStatus(), FundStatusEnums.CANCEL.getCode())) {
            return;
        }
        if (StringUtils.equals(userBalanceRecord.getStatus(), FundStatusEnums.FINISH.getCode())) {
            log.error("取消失败，流水：{}已入账成功", bizOrderNo);
        }
        userBalanceRecord.setStatus(FundStatusEnums.CANCEL.getCode());
        userBalanceRecord.setUpdateTime(LocalDateTime.now());
        userBalanceRecordDao.updateById(userBalanceRecord);

        Pair<User, UserFundAccount> userPair = this.queryAndCheckFundAccount(userBalanceRecord.getUid(), FundAccountType.CRAB_ROE.name());
        UserFundAccount userFundAccount = userPair.getRight();

        userFundAccount.setFrozenAmount(userFundAccount.getFrozenAmount().subtract(userBalanceRecord.getAmount()));
        userFundAccount.setTotalAmount(userFundAccount.getTotalAmount().subtract(userBalanceRecord.getAmount()));
        this.checkFund(userFundAccount.getFundAccount(), userFundAccount.getTotalAmount(), userFundAccount.getAvailableAmount(), userFundAccount.getFrozenAmount());
        userFundAccountDao.updateById(userFundAccount);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addCrabRoeConfirm(String bizOrderNo) {
        // 根据业务单号查询资金流水记录
        LambdaQueryWrapper<UserBalanceRecord> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(UserBalanceRecord::getLinkId, bizOrderNo);
        wrapper.eq(UserBalanceRecord::getDeleted, BooleanEnums.FALSE.getCode());
        UserBalanceRecord userBalanceRecord = userBalanceRecordDao.selectOne(wrapper);
        if (Objects.isNull(userBalanceRecord)) {
            log.error("未查询到对应的雅识添加申请记录");
            return;
        }
        if (StringUtils.equals(userBalanceRecord.getStatus(), FundStatusEnums.FINISH.getCode())) {
            log.error("业务单号{}已完成", bizOrderNo);
            return;
        }
        if (StringUtils.equals(userBalanceRecord.getStatus(), FundStatusEnums.CANCEL.getCode())) {
            log.error("业务单号{}已取消", bizOrderNo);
            return;
        }

        // 校验用户、账户信息
        Pair<User, UserFundAccount> userPair = this.queryAndCheckFundAccount(userBalanceRecord.getUid(), FundAccountType.CRAB_ROE.name());
        UserFundAccount userFundAccount = userPair.getRight();

        // 将资金从冻结金额转移到可用中
        log.info("用户资金账户可用金额：{}，冻结金额：{}", userFundAccount.getAvailableAmount(), userFundAccount.getFrozenAmount());
        userFundAccount.setFrozenAmount(userFundAccount.getFrozenAmount().subtract(userBalanceRecord.getAmount()));
        userFundAccount.setAvailableAmount(userFundAccount.getAvailableAmount().add(userBalanceRecord.getAmount()));
        this.checkFund(userFundAccount.getFundAccount(), userFundAccount.getTotalAmount(), userFundAccount.getAvailableAmount(), userFundAccount.getFrozenAmount());
        userFundAccountDao.updateById(userFundAccount);
        log.info("修改用户资金账户可用金额：{}，冻结金额：{}", userFundAccount.getAvailableAmount(), userFundAccount.getFundAccount());

        // 更新记录信息
//        userBalanceRecord.setAvailableAmount(userFundAccount.getAvailableAmount());
//        userBalanceRecord.setTransitAmount(userFundAccount.getFrozenAmount());
        userBalanceRecord.setStatus(FundStatusEnums.FINISH.getCode());
        userBalanceRecordDao.updateById(userBalanceRecord);

        // 修改用户信息中的可用金额
        User user = userPair.getLeft();
        userDao.updateAvailableAmount(user.getId(), userFundAccount.getAvailableAmount());
    }

    public Pair<User, UserFundAccount> queryAndCheckFundAccount(Integer userId, String accountType) {
        // 查询用户状态
        User user = userManager.findByUserId(userId);
        if (user == null || !user.getStatus()) {
            log.error("用户{}状态异常", userId);
            throw new CrmebException("用户异常");
        }

        // 校验用户资金账户状态
        UserFundAccount userFundAccount = this.findByIdAndAccountType(userId, accountType);
        if (Objects.isNull(userFundAccount)) {
            log.error("为未查询到{}的雅识资金账户", userId);
            throw new CrmebException("用户账户信息异常");
        }
        return Pair.of(user, userFundAccount);
    }

    private void checkFund(String account, BigDecimal total, BigDecimal available, BigDecimal frozen) {
        if (total.compareTo(available.add(frozen)) != 0) {
            log.error("资金账户{}信息异常,total:{},available:{},frozen:{}", account, total, available, frozen);
            throw new CrmebException("资金账户信息异常");
        }
    }

    @Override
    public UserFundAccount addAvailableAmount(Integer userId, BigDecimal amount, String fundAccountType) {
        User user = userManager.findByUserId(userId);
        return addAvailableAmount(user, amount, fundAccountType);
    }

    @Override
    public UserFundAccount addAvailableAmount(User user, BigDecimal amount, String fundAccountType) {
        UserFundAccount userFundAccount = fundAccount(user.getId(), fundAccountType);
        this.crabRoeCheck(user, userFundAccount);

        userFundAccount.setTotalAmount(userFundAccount.getTotalAmount().add(amount));
        userFundAccount.setAvailableAmount(userFundAccount.getAvailableAmount().add(amount));
        userFundAccount.setUpdateTime(LocalDateTime.now());
        userFundAccountDao.updateById(userFundAccount);

        userDao.updateAvailableAmount(userFundAccount.getUserId(), userFundAccount.getAvailableAmount());

        return userFundAccount;
    }

    @Override
    public UserFundAccount minusAvailableAmount(Integer userId, BigDecimal amount, String fundAccountType) {
        User user = userManager.findByUserId(userId);
        return this.minusAvailableAmount(user, amount, fundAccountType);
    }

    @Override
    public UserFundAccount minusAvailableAmount(User user, BigDecimal amount, String fundAccountType) {
        UserFundAccount userFundAccount = fundAccount(user.getId(), fundAccountType);
        this.crabRoeCheck(user, userFundAccount);
        // 判断余额是否足够
        if (userFundAccount.getAvailableAmount().compareTo(amount) < 0) {
            throw new CrmebException("雅识金额不足");
        }

        userFundAccount.setTotalAmount(userFundAccount.getTotalAmount().subtract(amount));
        userFundAccount.setAvailableAmount(userFundAccount.getAvailableAmount().subtract(amount));
        userFundAccount.setUpdateTime(LocalDateTime.now());
        userFundAccountDao.updateById(userFundAccount);

        userDao.updateAvailableAmount(userFundAccount.getUserId(), userFundAccount.getAvailableAmount());

        return userFundAccount;
    }

    @Override
    public UseAvailableResultDTO useAvailableAmount(User user, BigDecimal amount, String fundAccountType) {
        UserFundAccount userFundAccount = fundAccount(user.getId(), fundAccountType);
        this.crabRoeCheck(user, userFundAccount);
        // 如果足够，直接使用，如果不够，使用所有可用
        amount = (userFundAccount.getAvailableAmount().compareTo(amount) < 0) ? userFundAccount.getAvailableAmount() : amount;

        userFundAccount.setTotalAmount(userFundAccount.getTotalAmount().subtract(amount));
        userFundAccount.setAvailableAmount(userFundAccount.getAvailableAmount().subtract(amount));
        userFundAccount.setUpdateTime(LocalDateTime.now());
        userFundAccountDao.updateById(userFundAccount);

        userDao.updateAvailableAmount(userFundAccount.getUserId(), userFundAccount.getAvailableAmount());

        return UseAvailableResultDTO.builder().userFundAccount(userFundAccount).realAmount(amount).build();
    }

    @Override
    public UseAvailableResultDTO getAvailableAmount(User user, BigDecimal amount, String fundAccountType) {
        UserFundAccount userFundAccount = fundAccount(user.getId(), fundAccountType);
        this.crabRoeCheck(user, userFundAccount);
        // 如果足够，直接使用，如果不够，使用所有可用
        amount = (userFundAccount.getAvailableAmount().compareTo(amount) < 0) ? userFundAccount.getAvailableAmount() : amount;
        userFundAccount.setTotalAmount(userFundAccount.getTotalAmount().subtract(amount));
        userFundAccount.setAvailableAmount(userFundAccount.getAvailableAmount().subtract(amount));
        userFundAccount.setUpdateTime(LocalDateTime.now());
        return UseAvailableResultDTO.builder().userFundAccount(userFundAccount).realAmount(amount).build();
    }

    @Override
    public UseAvailableResultDTO useAvailableAmountAndRecordForOrderCreate(User user, String bizOrderNo, BigDecimal amount, String image) {
        final UserFundAccount userFundAccount = fundAccount(user.getId(), FundAccountType.CRAB_ROE.name());
        UseAvailableResultDTO build = this.useAvailableAmount(user, amount, FundAccountType.CRAB_ROE.name());
        // 记录操作记录
        saveUserBalanceRecord(SaveUserBalanceRecordDTO.buildByOrderCreate(userFundAccount, FundStatusEnums.FINISH, image, bizOrderNo, build.getRealAmount(), FundTradeTypeEnums.ORDER, FundDirectionEnums.OUTCOME));
        return build;
    }

    @Override
    public void backAvailableAmountAndRecordForOrderCancel(User user, String bizOrderNo, BigDecimal amount, String image) {
        UserFundAccount userFundAccount = fundAccount(user.getId(), FundAccountType.CRAB_ROE.name());
        this.addAvailableAmount(user, amount, FundAccountType.CRAB_ROE.name());
        // 记录操作记录
        saveUserBalanceRecord(SaveUserBalanceRecordDTO.buildByOrderCreate(userFundAccount, FundStatusEnums.FINISH, image, bizOrderNo, amount, FundTradeTypeEnums.ORDER_CANCEL, FundDirectionEnums.INCOME));
    }

    private void crabRoeCheck(User user, UserFundAccount userFundAccount) {
        if (userFundAccount.getAvailableAmount().compareTo(user.getNowMoney()) != 0) {
            log.error("资金账户异常，资金账户信息：{}，用户表可用余额：{}", JSONUtil.toJsonStr(userFundAccount), user.getNowMoney());
            throw new CrmebException("资金账户异常");
        }
    }

    @Override
    public void saveUserBalanceRecord(SaveUserBalanceRecordDTO userBalanceRecordDTO) {
        UserFundAccount userFundAccount = userBalanceRecordDTO.getUserFundAccount();
        UserBalanceRecord userBalanceRecord = new UserBalanceRecord();
        userBalanceRecord.setFundAccount(userFundAccount.getFundAccount());
        userBalanceRecord.setAccountType(userFundAccount.getAccountType());
        userBalanceRecord.setBillDate(DateTimeUtil.todayInt());
        userBalanceRecord.setStatus(userBalanceRecordDTO.getStatus());
        if (Objects.equals(userBalanceRecordDTO.getStatus(), FundStatusEnums.FINISH.getCode())) {
            userBalanceRecord.setTotalAmount(userFundAccount.getTotalAmount());
            userBalanceRecord.setAvailableAmount(userFundAccount.getAvailableAmount());
            userBalanceRecord.setFreezeAmount(userFundAccount.getFrozenAmount());
        }
        userBalanceRecord.setUid(userFundAccount.getUserId());
        userBalanceRecord.setLinkId(userBalanceRecordDTO.getBizOrderNo());
        userBalanceRecord.setLinkType(userBalanceRecordDTO.getFundType());
        userBalanceRecord.setType(userBalanceRecordDTO.getFundDirection());
        userBalanceRecord.setAmount(userBalanceRecordDTO.getTradeAmount());
        userBalanceRecord.setBalance(userFundAccount.getAvailableAmount());
        userBalanceRecord.setRemark(userBalanceRecordDTO.getRemark());
        userBalanceRecord.setCreateTime(LocalDateTime.now());
        userBalanceRecord.setUpdateTime(LocalDateTime.now());
        userBalanceRecord.setDeleted(BooleanEnums.FALSE.getCode());
        int insert = userBalanceRecordDao.insert(userBalanceRecord);
        if (BooleanEnums.isFalse(insert)) {
            log.error("保存雅识流水失败，userBalanceRecord:[{}]", JSONUtil.toJsonStr(userBalanceRecord));
            throw new CrmebException("保存雅识流水失败");
        }
    }
}
